#ifndef IPC_H
#define IPC_H

#include "SharedMemory.h"

#include <string>
#include <functional>

#include <thread>
#include <cstdint>
#include <vector>

template <class T>
class PacketPool
{
    struct Node
    {
        Node(char *data, size_t len) : data(data), len(len) {}
        char *data = nullptr;
        size_t len = 0;

        bool Used() const
        {
            if (data == nullptr)
                return false;
            return data[0] > 0;
        }

        void SetUsed(bool value)
        {
            if (data != nullptr)
                data[0] = value ? 0x01 : 0x00;
        }
    };
    std::vector<Node> nodeVec;

public:
    PacketPool(void *data, int len)
    {
        auto bufLen = sizeof(T) + 1;
        int size = len / bufLen;

        for (int i = 0; i < size; ++i)
        {
            auto cur = (char *)data + bufLen * i;
            nodeVec.emplace_back(cur, bufLen);
        }
    }

    bool Write(const T *pData)
    {
        bool ret = false;
        for (auto &node : nodeVec)
        {
            if (!node.Used())
            {
                memcpy(node.data + 1, pData, node.len - 1);
                ret = true;
                node.SetUsed(true);
                break;
            }
        }
        return ret;
    }

    bool Read(T *pData)
    {
        bool ret = false;
        for (auto &node : nodeVec)
        {
            if (node.Used())
            {
                memcpy(pData, node.data + 1, node.len - 1);
                ret = true;
                node.SetUsed(false);
                break;
            }
        }
        return ret;
    }
};

template <class T>
class IPCChannel final
{
public:
    explicit IPCChannel(const std::string &channelName, int totalSize = 10240)
    {
        sharedMem = std::make_shared<SharedMemory>(channelName);
        if (sharedMem->Attach(SharedMemory::ReadWrite))
        {
            msgPool = std::make_shared<PacketPool<T>>(sharedMem->Data(), sharedMem->DataLen());
        }
        else
        {
            if (sharedMem->Create(totalSize))
            {
                msgPool = std::make_shared<PacketPool<T>>(sharedMem->Data(), sharedMem->DataLen());
            }
            else
            {
                // Create failed, maybe we could throw exception
            }
        }
    }

    ~IPCChannel()
    {
        if (threadRead && threadRead->joinable())
        {
            autoRead.store(false);
            threadRead->join();
        }
        sharedMem->Detach();
    }

    // Whether the shared memory was successfully opened
    bool IsAttached()
    {
        return sharedMem->IsAttached();
    }

    // write msg to shared memory
    bool SendMsg(const T *msg)
    {
        sharedMem->Lock();
        auto ret = msgPool->Write(msg);
        sharedMem->Unlock();
        return ret;
    }

    // Start a background thread and automatically read msg in shared memory
    void BeginAutoRead(std::function<void(const T *)> func, int interval = 1000)
    {
        if (!threadRead)
        {
            threadRead = std::make_shared<std::thread>([this](std::function<void(const T *)> func, int interval)
                                                       { ReadProc(func, interval); },
                                                       func, interval);
        }
    }

protected:
    void ReadProc(std::function<void(const T *)> func, int interval)
    {
        T data;
        autoRead.store(true);
        while (autoRead.load())
        {
            if (sharedMem)
            {
                sharedMem->Lock();

                if (msgPool && msgPool->Read(&data))
                {
                    if (func)
                    {
                        func(&data);
                    }
                }
                sharedMem->Unlock();
            }

            std::this_thread::sleep_for(std::chrono::milliseconds(interval));
        }
    }

private:
    std::atomic_bool autoRead{false};

private:
    std::shared_ptr<std::thread> threadRead{nullptr};
    std::shared_ptr<SharedMemory> sharedMem{nullptr};
    std::shared_ptr<PacketPool<T>> msgPool{nullptr};
};

#endif // IPC_H